package com.eastdigit.system.service;

import com.eastdigit.system.inter.DataScopeMapper;
import com.eastdigit.system.model.*;
import com.eastdigit.util.LogUtil;
import com.eastdigit.util.PropertiesUtil;
import com.eastdigit.util.StringUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.stereotype.Service;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Service
public class DataScopeService extends BaseService implements IDataScopeService {

    @Autowired
    private DataScopeMapper dataScopeMapper;
    @Autowired
    private IDepartmentService departmentService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private UserRoleService userRoleService;

    private Lock lock = new ReentrantReadWriteLock().writeLock();

    private final boolean UPDATE_REAL_TIME = "true".equals(PropertiesUtil
            .getProperty("project.data.scope.update_real_time"));

    /**
     * 表关联获取角色部门的直接范围，后续要处理角色部门包含下级的情况
     * 
     * @return
     */
    private SqlBuilder getCaclScopeSqlBuilder() {
        return new SqlBuilder(
                "select userRole.USER_ID, roleMenu.MENU_ID, role.DEPT_ID, role.ROLE_ID, role.C_ROLE_SCOPE")
                .appendSql(" from SYS_USER_ROLE userRole, SYS_ROLE_MENU roleMenu, SYS_ROLE role")
                .appendSql(" where userRole.ROLE_ID = role.ROLE_ID and roleMenu.ROLE_ID = role.ROLE_ID")
                .appendSql(
                        " and not exists(select 1 from SYS_MENU where PARENT_MENU_ID = roleMenu.MENU_ID and C_MENU_TYPE = ?)",
                        Constants.MENU_TYPE_MENU);
    }

    /**
     * 全部重新加载
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void reload() {
        try {
            LogUtil.getLogger().debug("--开始加载数据范围--");
            Map<String, DataScope> map = new HashMap<String, DataScope>();
            JSONArray array = query(getCaclScopeSqlBuilder());
            for (int i = 0; i < array.size(); i++) {
                JSONObject json = array.getJSONObject(i);
                String deptId = json.getString("deptId"), userId = json.getString("userId"), menuId = json
                        .getString("menuId");
                // 添加当前部门
                DataScope scope = new DataScope(userId, menuId, deptId);
                map.put(scope.toString(), scope);
                // 添加下级部门
                if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(json.getString("roleScope"))) {// 角色数据范围包含下级
                    List<IDepartment> descendants = departmentService.queryDescendantDepartment(deptId);
                    for (IDepartment dept : descendants) {
                        scope = new DataScope(userId, menuId, dept.getDeptId());
                        map.put(scope.toString(), scope);
                    }
                }
            }
            List<DataScope> list = new ArrayList<DataScope>();
            list.addAll(map.values());

            dataScopeMapper.deleteAll();
            batchInserDataScope(list);
            LogUtil.getLogger().debug("--数据范围加载完毕--");
        } catch (Exception e) {
            LogUtil.getLogger().error(e.getMessage());
        }
    }

    /**
     * 查询某个用户的某个菜单的数据范围
     * 
     * @param userId
     * @param menuId
     * @return
     */
    @Override
    public List<DataScope> queryMenuDataScopeOfUser(String userId, String menuId) {
        return dataScopeMapper.selectDataScopeByUserAndMenuId(userId, menuId);
    }

    /**
     * 查询某个用户的某个菜单的数据范围
     * 
     * @param userId
     * @param menuIds
     * @return
     */
    @Override
    public List<DataScope> queryMenuDataScopeOfUser(String userId, List<String> menuIds) {
        return dataScopeMapper.selectDataScopeByUserAndMenuIds(userId, menuIds);
    }

    /**
     * 获取用户拥有的某个功能点能够操作的部门
     * 
     * @param userId
     * @return
     */
    @Cacheable(value = "authorizationCache", key = "'datascope_' + #userId")
    public JSONObject queryUrlDataScopeOfUser(String userId) {
        JSONObject jsonObject = new JSONObject();
        SqlBuilder sqlBuilder = new SqlBuilder(
                "select a.MENU_ID, b.URL, b.C_MENU_TYPE, a.DEPT_ID from SYS_DATA_SCOPE a, SYS_MENU b where a.MENU_ID = b.MENU_ID and a.USER_ID = ?",
                userId);
        JSONArray array = this.query(sqlBuilder);

        for (int i = 0; i < array.size(); i++) {
            JSONObject json = array.getJSONObject(i);
            String menuType = json.getString("menuType"), deptId = json.getString("deptId");
            // 如果是功能点，设置功能点，否则设置菜单
            String key = Constants.MENU_TYPE_FUNCTION.equals(menuType) ? json.getString("url") : json
                    .getString("menuId");
            String deptIds = jsonObject.optString(key);
            jsonObject.put(key, StringUtil.isEmpty(deptIds) ? deptId : deptIds + "," + deptId);

        }
        return jsonObject;
    }

    /**
     * 获取受到某个角色影响的所有用户的所有数据范围
     * 
     * @param roleId
     * @return
     */
    private List<UserScope> queryUserScopeOfRoleInfluence(String roleId) {
        SqlBuilder sqlBuilder = getCaclScopeSqlBuilder().appendSql(
                " and exists(select 1 from SYS_USER_ROLE where USER_ID = userRole.USER_ID and ROLE_ID = ?)", roleId);
        JSONArray array = this.query(sqlBuilder);

        List<UserScope> list = new ArrayList<UserScope>();

        for (int i = 0; i < array.size(); i++) {
            JSONObject json = array.getJSONObject(i);
            String userId = json.getString("userId"), roleId2 = json.getString("roleId"), menuId = json
                    .getString("menuId"), deptId = json.getString("deptId"), roleScope = json.getString("roleScope");
            // 添加当前部门
            list.add(new UserScope(userId, roleId2, menuId, deptId, roleScope));

            // 添加下级部门
            if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(roleScope)) {// 角色数据范围包含下级
                List<IDepartment> departments = departmentService.queryDescendantDepartment(deptId);
                for (IDepartment department : departments) {
                    list.add(new UserScope(userId, roleId2, menuId, department.getDeptId(), roleScope));
                }
            }
        }
        return list;
    }

    /**
     * 修改角色，在角色修改提交前调用
     * 
     * @param roleId
     * @param roleScope
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByRoleUpdate(String roleId, String roleScope) {
        if (UPDATE_REAL_TIME) {
            lock.lock();
            try {
                Role role = roleService.queryById(roleId);
                if (role == null) {
                    throw new BusinessException("角色不存在！");
                }
                if (!role.getScope().equals(roleScope)) {// 角色范围改变了
                    List<IDepartment> descendants = departmentService.queryDescendantDepartment(role.getDeptId());
                    List<UserScope> relatives = queryUserScopeOfRoleInfluence(roleId);// 受到某个角色影响的所有用户的所有数据范围

                    if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(roleScope)) {// 新的角色数据范围包含下级
                        // copy当前角色所属部门的权限到所有下级
                        List<DataScope> adds = new ArrayList<DataScope>();
                        for (UserScope scope : relatives) {
                            if (scope.getRoleId().equals(roleId) && scope.getDeptId().equals(role.getDeptId())) {// 当前角色所属部门的数据范围
                                for (IDepartment dept : descendants) {
                                    DataScope dataScope = scope.getDataScope();
                                    dataScope.setDeptId(dept.getDeptId());
                                    adds.add(dataScope);
                                }
                            }
                        }
                        this.addExtraDataScope(relatives, adds);
                    } else {// 新的角色数据范围不包含下级
                        Map<String, String> map = new HashMap<String, String>();
                        for (IDepartment dept : descendants) {
                            map.put(dept.getDeptId(), "");
                        }
                        List<UserScope> deletes = new ArrayList<UserScope>();
                        for (UserScope scope : relatives) {
                            if (scope.getRoleId().equals(roleId) && map.containsKey(scope.getDeptId())) {// 是属于当前角色所属部门的下级部门
                                deletes.add(scope);
                            }
                        }
                        this.deleteDifferenceDataScope(roleId, relatives, deletes);
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 删除角色，在删除之前调用
     * 
     * @param roleId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByRoleDelete(String roleId) {
        if (UPDATE_REAL_TIME) {
            lock.lock();
            try {
                List<UserScope> relatives = queryUserScopeOfRoleInfluence(roleId), deletes = new ArrayList<UserScope>();
                for (UserScope scope : relatives) {
                    if (scope.getRoleId().equals(roleId)) {
                        deletes.add(scope);
                    }
                }
                deleteDifferenceDataScope(roleId, relatives, deletes);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 
     * 角色增加菜单
     * 
     * @param roleId
     * @param menuIds
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByRoleAddMenus(String roleId, String menuIds) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(menuIds)) {
            Role role = roleService.queryById(roleId);
            if (role == null) {
                throw new BusinessException("查找角色失败！");
            }
            SqlBuilder sqlBuilder = new SqlBuilder("select MENU_ID from SYS_MENU a where 1 = 1").addWhereIn("MENU_ID",
                    menuIds.split(",")).appendSql(
                    " and not exists(select 1 from SYS_MENU where PARENT_MENU_ID = a.MENU_ID and C_MENU_TYPE = ?)",
                    Constants.MENU_TYPE_MENU);
            JSONArray menus = query(sqlBuilder);// 这里是为了去掉上级菜单

            List<DataScope> list = new ArrayList<DataScope>();
            List<UserRole> userRoles = userRoleService.queryRolesAssignedToUser(roleId);// 拥有该角色的用户

            if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(role.getScope())) {// 角色数据范围包含下级
                for (IDepartment dept : (List<IDepartment>) departmentService.queryDescendantDepartmentAndSelf(role
                        .getDeptId())) {
                    for (UserRole userRole : userRoles) {
                        for (int i = 0; i < menus.size(); i++) {
                            list.add(new DataScope(userRole.getUserId(), menus.getJSONObject(i).getString("menuId"),
                                    dept.getDeptId()));
                        }
                    }
                }
            } else {// 仅自己
                for (UserRole userRole : userRoles) {
                    for (int i = 0; i < menus.size(); i++) {
                        list.add(new DataScope(userRole.getUserId(), menus.getJSONObject(i).getString("menuId"), role
                                .getDeptId()));
                    }
                }
            }
            lock.lock();
            try {
                batchDeleteDataScope(list);
                batchInserDataScope(list);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 角色删除菜单
     * 
     * @param roleId
     * @param menuIds
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByRoleDeleteMenus(String roleId, String menuIds) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(menuIds)) {
            lock.lock();
            try {
                List<UserScope> relatives = queryUserScopeOfRoleInfluence(roleId), deletes = new ArrayList<UserScope>();
                for (UserScope scope : relatives) {
                    if (scope.getRoleId().equals(roleId)
                            && ("," + menuIds + ",").contains("," + scope.getMenuId() + ",")) {
                        deletes.add(scope);
                    }
                }
                deleteDifferenceDataScope(roleId, relatives, deletes);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 
     * 用户增加角色
     * 
     * @param userId
     * @param roleId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByUserAddRole(String userId, String roleId) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(roleId)) {
            List<DataScope> list = new ArrayList<DataScope>();
            Role role = roleService.queryById(roleId);
            if (role == null) {
                throw new BusinessException("查找用户失败！");
            }
            List<IDepartment> descendants = departmentService.queryDescendantDepartment(role.getDeptId());
            JSONArray array = query(
                    "select ROLE_ID,MENU_ID from SYS_ROLE_MENU roleMenu where ROLE_ID = ?"
                            + " and not exists(select 1 from SYS_MENU where PARENT_MENU_ID = roleMenu.MENU_ID and C_MENU_TYPE = ?)",
                    roleId, Constants.MENU_TYPE_MENU);
            // 角色包含的菜单
            for (int i = 0; i < array.size(); i++) {
                JSONObject json = array.getJSONObject(i);
                String menuId = json.getString("menuId");
                list.add(new DataScope(userId, menuId, role.getDeptId()));
                // 添加下级
                if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(role.getScope())) {// 角色数据范围包含下级
                    for (IDepartment dept : descendants) {
                        list.add(new DataScope(userId, menuId, dept.getDeptId()));
                    }
                }
            }
            lock.lock();
            try {
                batchDeleteDataScope(list);
                batchInserDataScope(list);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 
     * 用户删除角色，删除之前执行
     * 
     * @param userId
     * @param roleId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByUserDeleteRole(String userId, String roleId) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(roleId)) {
            lock.lock();
            try {
                List<UserScope> relatives = queryUserScopeOfRoleInfluence(roleId), deletes = new ArrayList<UserScope>();
                for (UserScope scope : relatives) {
                    if (scope.getUserId().equals(userId) && scope.getRoleId().equals(roleId)) {
                        deletes.add(scope);
                    }
                }
                deleteDifferenceDataScope(roleId, relatives, deletes);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 
     * 用户删除
     * 
     * @param userId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByUserDelete(String userId) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(userId)) {
            lock.lock();
            try {
                this.delete("delete from SYS_DATA_SCOPE where USER_ID = ?", userId);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 
     * 菜单删除
     * 
     * @param menuId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByMenuDelete(String menuId) {
        if (UPDATE_REAL_TIME && StringUtil.isNotEmpty(menuId)) {
            lock.lock();
            try {
                this.delete("delete from SYS_DATA_SCOPE where MENU_ID = ?", menuId);
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 增加部门，增加之后调用
     * 
     * @param deptId
     * @param parentDeptId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByDeptAdd(String deptId, String parentDeptId) {
        if (UPDATE_REAL_TIME) {
            // 首先找到该部门的所有上级部门
            List<Object> sncestorIds = new ArrayList<Object>();
            sncestorIds.add(parentDeptId);
            if (!Constants.DEPT_ROOT_PARENT_ID.equals(parentDeptId)) {
                List<IDepartment> ancestors = departmentService.queryAncestorDepartment(parentDeptId);
                for (IDepartment dept : ancestors) {
                    sncestorIds.add(dept.getDeptId());
                }
            }
            // 查找增加的部门的祖先所影响的DataScope
            JSONArray array = query(getCaclScopeSqlBuilder().addWhereIn("role.DEPT_ID", sncestorIds));
            // 这里要考虑到修改调用该方法存在下级部门的情况
            List<IDepartment> descendants = departmentService.queryDescendantDepartment(deptId);
            Map<String, DataScope> map = new HashMap<String, DataScope>();// 避免重复

            for (int i = 0; i < array.size(); i++) {
                JSONObject json = array.getJSONObject(i);
                String userId = json.getString("userId"), menuId = json.getString("menuId"), roleScope = json
                        .getString("roleScope");
                // 这里是部门都属于当前添加的上级，所以，如果上级定义的角色包含了下级，就添加到DataScope
                if (Constants.ROLE_SCOPE_CONTAINS_SUBORDINATE.equals(roleScope)) {// 角色数据范围包含下级
                    DataScope dataScope = new DataScope(userId, menuId, deptId);
                    map.put(dataScope.toString(), dataScope);
                    // 增加下级
                    for (IDepartment dept : descendants) {
                        dataScope = new DataScope(userId, menuId, dept.getDeptId());
                        map.put(dataScope.toString(), dataScope);
                    }
                }
            }
            if (map.size() > 0) {
                List<DataScope> list = new ArrayList<DataScope>();
                list.addAll(map.values());
                lock.lock();
                try {
                    batchInserDataScope(list);
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 删除部门，删除前执行
     * 
     * @param deptId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByDeptDelete(String deptId) {
        if (UPDATE_REAL_TIME) {
            lock.lock();
            try {
                List<IDepartment> depts = departmentService.queryDescendantDepartmentAndSelf(deptId);
                List<Object> deptIds = new ArrayList<Object>();
                for (IDepartment dept : depts) {
                    deptIds.add(dept.getDeptId());
                }
                if (deptIds.size() == 0) {
                    throw new BusinessException("查找部门失败！");
                }
                delete(new SqlBuilder("delete from SYS_DATA_SCOPE where 1 = 1").addWhereIn("DEPT_ID", deptIds));
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 部门修改
     * 
     * @param deptId
     * @param parentDeptId
     */
    @Override
    @CacheEvict(value = "authorizationCache", allEntries = true)
    public void updateByDeptUpdate(String deptId, String parentDeptId) {
        if (UPDATE_REAL_TIME) {
            IDepartment department = departmentService.queryDepartment(deptId);
            if (department == null) {
                throw new BusinessException("查找部门失败！");
            }
            if (!department.getParentDeptId().equals(parentDeptId)) {// 上级部门改变了
                this.updateByDeptDelete(deptId);
                this.updateByDeptAdd(deptId, parentDeptId);
            }
        }
    }

    /**
     * 添加额外的
     * 
     * @param existsRelativeScopes
     * @param adds
     */
    private void addExtraDataScope(List<UserScope> existsRelativeScopes, List<DataScope> adds) {
        Map<String, String> map = new HashMap<String, String>();
        for (UserScope userScope : existsRelativeScopes) {
            map.put(userScope.getKey(), "");
        }
        List list = new ArrayList();
        for (DataScope scope : adds) {
            if (!map.containsKey(scope.getUserId() + ":" + scope.getMenuId() + ":" + scope.getDeptId())) {
                list.add(scope);
            }
        }
        batchInserDataScope(list);
    }

    /**
     * 删除某个角色存在差异的
     * 
     * @param roleId
     * @param relatives
     */
    private void deleteDifferenceDataScope(String roleId, List<UserScope> relatives, List<UserScope> deletes) {
        // 查询出拥有该角色的用户、以及关联的菜单
        if (deletes != null && deletes.size() > 0) {
            Map<String, String> map = new HashMap<String, String>();
            for (UserScope scope : relatives) {
                // 缓存其他角色设置的DataScope
                if (!roleId.equals(scope.getRoleId())) {
                    map.put(scope.getKey(), "");
                }
            }
            List<DataScope> list = new ArrayList<DataScope>();
            for (UserScope scope : deletes) {
                if (!map.containsKey(scope.getKey())) {
                    list.add(scope.getDataScope());
                }
            }

            batchDeleteDataScope(list);
        }
    }

    /**
     * 批量插入
     * 
     * @param list
     */
    private void batchInserDataScope(List<DataScope> list) {
        while (list.size() > 0) {
            int end = Math.min(50, list.size());
            List<DataScope> list1 = list.subList(0, end);
            list = list.subList(end, list.size());
            dataScopeMapper.insertBatch(list1);
        }
    }

    /**
     * 批量删除
     * 
     * @param list
     */
    private void batchDeleteDataScope(final List<DataScope> list) {
        String sql = "delete from SYS_DATA_SCOPE where USER_ID = ? and MENU_ID = ? and DEPT_ID = ?";
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(java.sql.PreparedStatement ps, int i) throws SQLException {
                DataScope scope = list.get(i);
                ps.setString(1, scope.getUserId());
                ps.setString(2, scope.getMenuId());
                ps.setString(3, scope.getDeptId());
            }

            @Override
            public int getBatchSize() {
                return list.size();
            }
        });
    }

    class UserScope {
        private String userId, roleId, menuId, deptId, roleScope;

        public UserScope(String userId, String roleId, String menuId, String deptId, String roleScope) {
            this.userId = userId;
            this.roleId = roleId;
            this.menuId = menuId;
            this.deptId = deptId;
            this.roleScope = roleScope;
        }

        public String getKey() {
            return userId + ":" + menuId + ":" + deptId;
        }

        public DataScope getDataScope() {
            return new DataScope(userId, menuId, deptId);
        }

        public String getUserId() {
            return userId;
        }

        public String getRoleId() {
            return roleId;
        }

        public String getMenuId() {
            return menuId;
        }

        public String getDeptId() {
            return deptId;
        }

        public void setDeptId(String deptId) {
            this.deptId = deptId;
        }
    }
}
